﻿using System;
using System.Collections.Generic;
using System.Linq;
using AcademicPlanner.Data;
using AcademicPlanner.Data.Entities;
using AcademicPlanner.Data.Repositories;
using AcademicPlanner.Framework.Libraries.Validation;
using AcademicPlanner.Framework.Helpers;

namespace AcademicPlanner.Domain.Models
{
	/// <summary>
	/// User represents a user of the application in strictly acess control terms.
	/// </summary>
	/// <version>1.0</version>
	/// <since>1.0</since>
    public class User : Model
    {
		/// <summary>
		/// Minimum number of characters allowed for a password.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public static readonly int PASSWORD_MIN_LENGTH = 6;

		/// <summary>
		/// Maximum number of characters allowed for a password.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public static readonly int PASSWORD_MAX_LENGTH = 15;

		/// <summary>
		/// Maximum number of characters allowed for a username.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public static readonly int NAME_MAX_LENGTH = 20;

		static readonly string PASSWORD_HASH_SALT = ";;;academic-planner;;user-pswd;;;*";

		/// <summary>
		/// Regular expression of format and characters allowed for a password: "^[a-zA-Z0-9_&\*\$#@!\?]{6,16}$".
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public static readonly string PASSWORD_REGEXP = @"^[a-zA-Z0-9_&\*\$#@!\?]{6,16}$";


		/// <summary>
		/// Gets the data layer repository for this User's underlying data representation.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		protected override EntityRepository EntityRepo { get { return UserEntityRepository.GetInstance(); } }

		/// <summary>
		/// Gets User's underlying data representation.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		protected UserEntity Entity { get { return (UserEntity)entity; } }

		/// <summary>
		/// Gets this User's username.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public string Name { get { return Entity.Name; } }

		/// <summary>
		/// Gets this User's password.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public string Password { get { return Entity.Password; } }

		/// <summary>
		/// Gets whether this User is active or not.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public bool IsActive { get { return Entity.Active; } }


		/// <summary>
		/// No argument constructor; invokes the matching base class constructor.
		/// </summary>
		/// <seealso cref="M:AcademicPlanner.Domain.Model.Model"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public User()
			: base()
		{

		}

		/// <summary>
		/// Initializes a User with the given <c>UserEntity</c> by invoking the matching base class constructor.
		/// </summary>
		/// <seealso cref="M:AcademicPlanner.Domain.Model.Model(Entity)"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public User(UserEntity pEntity)
			: base(pEntity)
		{
			
		}


		/// <summary>
		/// Sets this User's properties after validating the provided values.
		/// <c>null</c> values are ignored and the corresponding properties are
		/// left unchanged. The username is canonicalized with spaces replaces
		/// with "_" and the password is hashed.
		/// </summary>
		/// <param name="pName">The new username.</param>
		/// <param name="pPassword">The new password.</param>
		/// <exception cref="T:InvalidModelPropertiesException">
		/// Thrown with an error code of <c>DomainErrorCode.M_INVALID_USER_PROPERTIES</c>
		/// if validation errors were returned.
		/// </exception>
		/// <seealso cref="M:Validate"/>
		/// <seealso cref="M:AcademicPlanner.Framework.Helpers.SecurityHelper.Hash"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public void Set(string pName, string pPassword)
		{
			var vErrs = Validate(pName, pPassword);

			if (vErrs.OuterErrors.Count > 0)
				throw new InvalidModelPropertiesException(DomainErrorCode.M_INVALID_USER_PROPERTIES, vErrs);

			if (pName != null)
				Entity.Name = pName.Replace(" ", "_").ToLower();
			if (pPassword != null)
				Entity.Password = SecurityHelper.Hash(pPassword, PASSWORD_HASH_SALT);
		}

		/// <summary>
		/// Validates possible values of a User's properties. <c>null</c> values are ignored.
		/// </summary>
		/// <param name="pName">The username.</param>
		/// <param name="pPassword">The password.</param>
		/// <returns>A Collection of validation errors.</returns>
		/// <seealso cref="M:ValidateName"/>
		/// <seealso cref="M:ValidatePassword"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public static InvalidModelPropertiesErrorCollection Validate(string pName, string pPassword)
		{
			var rVal = new InvalidModelPropertiesErrorCollection("User");
			InvalidModelPropertyErrorCode vCode;

			if (pName != null)
			{
				if (!(vCode = ValidateName(pName)).Equals(InvalidModelPropertyErrorCode.NONE))
					rVal.AddOuter("Name", vCode);
			}

			if (pPassword != null)
			{
				if (!(vCode = ValidatePassword(pPassword)).Equals(InvalidModelPropertyErrorCode.NONE))
					rVal.AddOuter("Password", vCode);
			}

			return rVal;
		}

		/// <summary>
		/// Validates a possible value of a User's username. Usernames must be
		/// not blank, aplhanumeric, shorter than <see cref="P:NAME_MAX_LENGTH"/>
		/// and match the criteria specified by 
		/// <see cref="M:AcademicPlanner.Domain.Components.AuthMngr.IsUserNameAllowed"/>.
		/// </summary>
		/// <param name="pVal">The value to validate.</param>
		/// <returns>A validation error code.</returns>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.IsNotBlank"/>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.IsAlphaNumeric"/>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.ContainsAtMost"/>
		/// <seealso cref="P:AcademicPlanner.Domain.DomainMaster.Auth"/>
		/// <seealso cref="M:AcademicPlanner.Domain.Components.AuthMngr.IsUserNameAllowed"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public static InvalidModelPropertyErrorCode ValidateName(string pVal)
		{
			var rVal = InvalidModelPropertyErrorCode.NONE;

			if (!StringValidator.IsNotBlank(pVal))
				rVal = InvalidModelPropertyErrorCode.REQUIRED;
			else if (!StringValidator.IsAlphaNumeric(pVal))
				rVal = InvalidModelPropertyErrorCode.NOT_ALPHANUMERIC;
			else if (!StringValidator.ContainsAtMost(pVal, NAME_MAX_LENGTH))
				rVal = InvalidModelPropertyErrorCode.TOO_LONG;
			else if (!DomainMaster.GetInstance().Auth.IsUserNameAllowed(pVal))
				rVal = InvalidModelPropertyErrorCode.DISALLOWED_SEQUENCE;

			return rVal;
		}

		/// <summary>
		/// Validates a possible value of a User's password. Passwords must be
		/// not blank, longer than <see cref="P:PASSWORD_MIN_LENGTH"/>, shorter than
		/// <see cref="P:PASSWORD_MAX_LENGTH"/> and match the regular expression
		/// <see cref="P:PASSWORD_REGEXP"/>.
		/// </summary>
		/// <param name="pVal">The value to validate.</param>
		/// <returns>A validation error code.</returns>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.IsNotBlank"/>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.ContainsAtLeast"/>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.ContainsAtMost"/>
		/// <seealso cref="M:AcademicPlanner.Framework.Libraries.Validation.StringValidator.Matches"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public static InvalidModelPropertyErrorCode ValidatePassword(string pVal)
		{
			var rVal = InvalidModelPropertyErrorCode.NONE;

			if (!StringValidator.IsNotBlank(pVal))
				rVal = InvalidModelPropertyErrorCode.REQUIRED;
			else if (!StringValidator.ContainsAtLeast(pVal, PASSWORD_MIN_LENGTH))
				rVal = InvalidModelPropertyErrorCode.TOO_SHORT;
			else if (!StringValidator.ContainsAtMost(pVal, PASSWORD_MAX_LENGTH))
				rVal = InvalidModelPropertyErrorCode.TOO_LONG;
			else if (!StringValidator.Matches(pVal, PASSWORD_REGEXP))
				rVal = InvalidModelPropertyErrorCode.INVALID_FORMAT;

			return rVal;
		}
    }
}
